{
    "cells": [
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "# Molecular docking via DC-QAOA\n",
                "\n",
                "Drugs often work by binding to an active site of a protein, inhibiting or activating its function for some therapeutic purpose. Finding new candidate drugs is extremely difficult. The study of molecular docking helps guide this search and involves the prediction of how strongly a certain ligand (drug) will bind to its target (usually a protein).  \n",
                "\n",
                "One of the primary challenges to molecular docking arises from the many geometric degrees of freedom present in proteins and ligands, making it difficult to predict the optimal orientation and assess if the drug is a good candidate or not. One solution is to formulate the problem as a mathematical optimization problem where the optimal solution corresponds to the most likely ligand-protein configuration. This optimization problem can be solved on a quantum computer using methods like the Quantum Approximate Optimization Algorithm (QAOA). This tutorial demonstrates how this [paper](https://arxiv.org/pdf/2308.04098) used digitized-counteradiabatic (DC) QAOA to study molecular docking.  This tutorial assumes you have an understanding of QAOA, if not, please see the CUDA-Q MaxCut tutorial found [here](https://nvidia.github.io/cuda-quantum/latest/applications/python/qaoa.html).\n",
                "\n",
                "The next section provides more detail on the problem setup followed by CUDA-Q implementations below."
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "### Setting up the Molecular Docking Problem\n",
                "\n",
                "The figure from the [paper](https://arxiv.org/pdf/2308.04098) provides a helpful diagram for understanding the workflow.\n",
                "\n",
                "![docking](./images/docking.png)\n",
                "\n",
                "\n",
                "There are 6 key steps:\n",
                "1.  The experimental protein and ligand structures are determined and used to select pharmacores, or an important chemical group that will govern the chemical interactions.\n",
                "2. Two labeled distance graphs (LAGs) of size $N$ and $M$ represent the protein and the ligand, respectively. Each node corresponds to a pharmacore and each edge weight corresponds to the distance between pharmacores.\n",
                "3.  A $M*N$ node binding interaction graph (BIG) is created from the LAGs. Each node in the BIG graph corresponds to a pair of pharmacores, one from the ligand and the other from the protein. The existence of edges between nodes in the BIG graph are determined from the LAGs and correspond to interactions that can feesibly coexist. Therefore, cliques in the graph correspond to mutually possible interactions. \n",
                "4. The problem is mapped to a QAOA circuit and corresponding Hamiltonian. From there, the ground state solution is determined.\n",
                "5.  The ground state will produce the maximum weighted clique which corresponds to the best (most strongly bound) orientation of the ligand and protein.\n",
                "6.  The predicted docking structure is interpreted from the QAOA result and is used for further analysis.\n"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "### CUDA-Q Implementation\n",
                "\n",
                "First, the appropriate libraries are imported and the `nvidia` backend is selected to run on GPUs if available."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 6,
            "metadata": {},
            "outputs": [],
            "source": [
                "import cudaq\n",
                "from cudaq import spin\n",
                "import numpy as np\n"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "The block below defines two of the BIG data sets from the paper. The first is a smaller example, but it can be swapped with the commented out example below at your discretion. The weights are specified for each node based on the nature of the ligand and protein pharmacores represented by the node."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 7,
            "metadata": {},
            "outputs": [
                {
                    "name": "stdout",
                    "output_type": "stream",
                    "text": [
                        "Edges:  [[0, 1], [0, 2], [0, 4], [0, 5], [1, 2], [1, 3], [1, 5], [2, 3], [2, 4], [3, 4], [3, 5], [4, 5]]\n",
                        "Non-Edges:  [[0, 3], [1, 4], [2, 5]]\n"
                    ]
                }
            ],
            "source": [
                "# The two graph inputs from the paper\n",
                "\n",
                "# BIG 1\n",
                "\n",
                "nodes = [0, 1, 2, 3, 4, 5]\n",
                "qubit_num = len(nodes)\n",
                "edges = [[0, 1], [0, 2], [0, 4], [0, 5], [1, 2], [1, 3], [1, 5], [2, 3], [2, 4],\n",
                "         [3, 4], [3, 5], [4, 5]]\n",
                "non_edges = [\n",
                "    [u, v] for u in nodes for v in nodes if u < v and [u, v] not in edges\n",
                "]\n",
                "\n",
                "print('Edges: ', edges)\n",
                "print('Non-Edges: ', non_edges)\n",
                "\n",
                "weights = [0.6686, 0.6686, 0.6686, 0.1453, 0.1453, 0.1453]\n",
                "penalty = 6.0\n",
                "num_layers = 3\n",
                "\n",
                "# BIG 2 (More expensive simulation)\n",
                "#nodes=[0,1,2,3,4,5,6,7]\n",
                "#qubit_num=len(nodes)\n",
                "#edges=[[0,1],[0,2],[0,5],[0,6],[0,7],[1,2],[1,4],[1,6],[1,7],[2,4],[2,5],[2,7],[3,4],[3,5],[3,6],\\\n",
                "#    [4,5],[4,6],[5,6]]\n",
                "#non_edges=[[u,v] for u in nodes for v in nodes if u<v and [u,v] not in edges]\n",
                "#print('Edges: ', edges)\n",
                "#print('Non-edges: ', non_edges)\n",
                "#weights=[0.6686,0.6686,0.6686,0.1091,0.0770,0.0770,0.0770,0.1943]\n",
                "#penalty=8.0\n",
                "#num_layers=8"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "Next, the Hamiltonian is constructed: \n",
                "\n",
                "$$H = \\frac{1}{2}\\sum_{i \\in V}w_i(\\sigma^z_i - 1) + \\frac{P}{4} \\sum_{(i,j) \\notin E, i \\neq j} (\\sigma^z_i -1)(\\sigma^z_j - 1) $$\n",
                "\n",
                "\n",
                "The first term concerns the vertices and the weights of the given pharmacores.  The second term is a penalty term that penalizes edges of the graph with no interactions.  The penalty $P$ is set by the user and is defined as 6 in the cell above. The function below returns the Hamiltonian as a CUDA-Q `spin_op` object.\n",
                "\n"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 8,
            "metadata": {},
            "outputs": [],
            "source": [
                "# Generate the Hamiltonian\n",
                "def ham_clique(penalty, nodes, weights, non_edges) -> cudaq.SpinOperator:\n",
                "\n",
                "    spin_ham = 0\n",
                "    for wt, node in zip(weights, nodes):\n",
                "        #print(wt,node)\n",
                "        spin_ham += 0.5 * wt * spin.z(node)\n",
                "        spin_ham -= 0.5 * wt * spin.i(node)\n",
                "\n",
                "    for non_edge in non_edges:\n",
                "        u, v = (non_edge[0], non_edge[1])\n",
                "        #print(u,v)\n",
                "        spin_ham += penalty / 4.0 * (spin.z(u) * spin.z(v) - spin.z(u) -\n",
                "                                     spin.z(v) + spin.i(u) * spin.i(v))\n",
                "\n",
                "    return spin_ham"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "The code below strips the Hamiltonian into a list of coefficients and corresponding Pauli words which can be passed into a quantum kernel."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 9,
            "metadata": {},
            "outputs": [
                {
                    "name": "stdout",
                    "output_type": "stream",
                    "text": [
                        "(0+0i) + (-1.1657+0i) * Z0 + (-0.3343+0i) * I0 + (-1.1657+0i) * Z1 + (-0.3343+0i) * I1 + (-1.1657+0i) * Z2 + (-0.3343+0i) * I2 + (-1.42735+0i) * Z3 + (-0.07265+0i) * I3 + (-1.42735+0i) * Z4 + (-0.07265+0i) * I4 + (-1.42735+0i) * Z5 + (-0.07265+0i) * I5 + (1.5+0i) * Z0Z3 + (1.5+0i) * I0I3 + (1.5+0i) * Z1Z4 + (1.5+0i) * I1I4 + (1.5+0i) * Z2Z5 + (1.5+0i) * I2I5\n",
                        "[0j, (-1.1657+0j), (-0.3343-0j), (-1.1657+0j), (-0.3343-0j), (-1.1657+0j), (-0.3343-0j), (-1.42735+0j), (-0.07265-0j), (-1.42735+0j), (-0.07265-0j), (-1.42735+0j), (-0.07265-0j), (1.5+0j), (1.5+0j), (1.5+0j), (1.5+0j), (1.5+0j), (1.5+0j)]\n",
                        "['IIIIII', 'ZIIIII', 'IIIIII', 'IZIIII', 'IIIIII', 'IIZIII', 'IIIIII', 'IIIZII', 'IIIIII', 'IIIIZI', 'IIIIII', 'IIIIIZ', 'IIIIII', 'ZIIZII', 'IIIIII', 'IZIIZI', 'IIIIII', 'IIZIIZ', 'IIIIII']\n"
                    ]
                }
            ],
            "source": [
                "# Collect coefficients from a spin operator so we can pass them to a kernel\n",
                "def term_coefficients(ham: cudaq.SpinOperator) -> list[complex]:\n",
                "    result = []\n",
                "    for term in ham:\n",
                "        result.append(term.evaluate_coefficient())\n",
                "    return result\n",
                "\n",
                "    # Collect Pauli words from a spin operator so we can pass them to a kernel\n",
                "\n",
                "\n",
                "def term_words(ham: cudaq.SpinOperator) -> list[str]:\n",
                "    # Our kernel uses these words to apply exp_pauli to the entire state.\n",
                "    # we hence ensure that each pauli word covers the entire space.\n",
                "    \n",
                "    result = []\n",
                "    for term in ham:\n",
                "        result.append(term.get_pauli_word(qubit_num))\n",
                "    return result\n",
                "\n",
                "\n",
                "ham = ham_clique(penalty, nodes, weights, non_edges)\n",
                "print(ham)\n",
                "\n",
                "coef = term_coefficients(ham)\n",
                "words = term_words(ham)\n",
                "\n",
                "print(term_coefficients(ham))\n",
                "print(term_words(ham))"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "The kernel below defines a DC-QAOA circuit.  What makes the approach \"DC\" is the inclusion of additional counteradiabatic terms to better drive the optimization to the ground state. These terms are digitized and applied as additional operations following each QAOA layer.  The increase in parameters is hopefully offset by requiring fewer layers. In this example, the DC terms are the additional parameterized $Y$ operations applied to each qubit. These can be commented out to run conventional QAOA."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 10,
            "metadata": {},
            "outputs": [],
            "source": [
                "@cudaq.kernel\n",
                "def dc_qaoa(qubit_num:int, num_layers:int, thetas:list[float],\\\n",
                "    coef:list[complex], words:list[cudaq.pauli_word]):\n",
                "\n",
                "    qubits = cudaq.qvector(qubit_num)\n",
                "\n",
                "    h(qubits)\n",
                "\n",
                "    count = 0\n",
                "    for p in range(num_layers):\n",
                "\n",
                "        for i in range(len(coef)):\n",
                "            exp_pauli(thetas[count] * coef[i].real, qubits, words[i])\n",
                "            count += 1\n",
                "\n",
                "        for j in range(qubit_num):\n",
                "            rx(thetas[count], qubits[j])\n",
                "            count += 1\n",
                "\n",
                "        #Comment out this for loop for conventional QAOA\n",
                "        for k in range(qubit_num):\n",
                "            ry(thetas[count], qubits[k])\n",
                "            count += 1"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "The classical optimizer for the QAOA procedure can be specified as one of the built-in CUDA-Q optimizers, in this case Nelder Mead. The parameter count is defined for DC-QAOA, but can be swapped with the commented line below for conventional QAOA."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 11,
            "metadata": {},
            "outputs": [
                {
                    "name": "stdout",
                    "output_type": "stream",
                    "text": [
                        "Total number of parameters:  93\n",
                        "Initial parameters =  [0.21810696323572243, -0.20613464375211488, 0.2546877639814583, 0.3657985647468064, 0.37118004688049144, -0.03656087558321203, 0.08564174998504231, 0.21639801853794682, 0.11122286088634259, 0.1743727097033635, -0.36518146001762486, -0.15829741539542244, -0.3467434780387345, 0.28043500852894776, -0.09986021299050934, 0.14125225086023052, -0.19141728018199775, -0.11970943368650361, -0.3853063093646483, -0.1112643868789806, 0.3527177454825464, -0.22156160012057186, -0.1418496891385843, 0.32811766468303116, -0.367642000671186, -0.34158180583996006, 0.10196745745501312, 0.29359239180502594, -0.3858537615546677, 0.19366130907065582, 0.24570488114056754, -0.3332307385378807, 0.12287973244618389, 0.007274514934614895, -0.015799547372526146, 0.3578070967202224, -0.39268963055535144, -0.19872246354138554, 0.16668715544467982, -0.13777293592446055, -0.17514665212709513, 0.15350249947988204, 0.32872977428061945, -0.20068831419712105, -0.032919322131134854, -0.19399909325771983, -0.09477141125241506, 0.08210460401106645, 0.21392577760158515, -0.3393568044538389, 0.14615087942938465, 0.03790339186006314, -0.2843250892879255, -0.3151384847055956, -0.19983741137121905, -0.27348611567665115, 0.33457528180906904, 0.14145414847455462, -0.20604220093940323, 0.05410235084309195, 0.04447870918600966, -0.3355714098595045, 0.266806440171265, -0.07436189654442632, -0.2789176729721685, -0.2427508182662484, -0.007351219158674538, 0.16652355413124942, 0.3808697740391629, 0.2943566301288585, -0.0007526597041920824, -0.3088342705284747, 0.3245365797019699, -0.10609475622422088, -0.21473737933378598, 0.29250730067679076, -0.28560348266311547, -0.20704653805110443, 0.07492639208926116, 0.050204699769995864, 0.3604464956189818, -0.036725741805424705, -0.2914156467694673, 0.20464937477384804, -0.2343360494257549, -0.2546811698770065, -0.04938739737483455, -0.1254588720659109, 0.3669797333052197, -0.2803666634362036, 0.27061563884884454, 0.13305127120906368, -0.3068512076427221]\n"
                    ]
                }
            ],
            "source": [
                "# Specify the optimizer and its initial parameters.\n",
                "optimizer = cudaq.optimizers.NelderMead()\n",
                "\n",
                "#Specify random seeds\n",
                "np.random.seed(13)\n",
                "cudaq.set_random_seed(13)\n",
                "\n",
                "# if dc_qaoa used\n",
                "parameter_count = (2 * qubit_num + len(coef)) * num_layers\n",
                "\n",
                "# if qaoa used\n",
                "# parameter_count=(qubit_num+len(coef))*num_layers\n",
                "\n",
                "print('Total number of parameters: ', parameter_count)\n",
                "optimizer.initial_parameters = np.random.uniform(-np.pi / 8, np.pi / 8,\n",
                "                                                 parameter_count)\n",
                "print(\"Initial parameters = \", optimizer.initial_parameters)"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "A cost function is specified which computes the expectation value of the DC-QAOA circuit and the Hamiltonian using the `observe` function. Running the optimization returns the minimized expectation value and the optimal parameters."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 12,
            "metadata": {},
            "outputs": [
                {
                    "name": "stdout",
                    "output_type": "stream",
                    "text": [
                        "optimal_expectation = -2.005710024421982\n",
                        "optimal_parameters = [1.8481888913540985, -0.2535399481030596, 0.22783474501904072, 2.081416556947703, 0.3374107641822874, -0.05224802127495901, 1.8679499667316164, 2.009143330388614, 0.1339973108317789, 0.2652751145535136, 1.324101519994548, 1.5559093725862327, -0.16508452001869578, 0.10055168392123187, 1.4335293611790534, -0.022802387382032274, -0.02345217217095934, 0.025049619373114198, -0.32382028011884517, 1.1592457772894376, 0.5114936288385474, -0.14306579846281064, -0.32504364746711095, 1.5987201329360095, -0.47283036988738203, -0.2879606126466135, 0.03195039154942317, 1.6485828925522248, -0.8013579153446403, -0.025680220483288878, 0.37302396767692614, 1.5188370864376863, 2.106660523866304, -0.09725921137896973, 0.17491777895553523, 0.5884424387073575, 1.0185444633156848, 1.2506750467643695, 1.5730001880356774, 0.923145372786352, -0.3016838127319703, 0.10130783752008649, 0.34049803711462423, -0.2742308863536732, -0.0843331582214344, -0.304728036625607, 0.03164048010752481, -0.16945553246060785, 0.21199415583984976, -0.333776464782457, 0.6762454214008358, 0.7077770344190998, -0.04315001481827706, -0.8685775873913754, -0.31256033232512964, -0.13726466931210604, -0.18103220051469332, -0.07014233979739845, -0.04255058220153306, 0.5213395683000333, -0.2522616199668185, -0.5922668326436884, 1.7533503557489256, -0.4243163991330685, 1.8047318156160967, -0.11962817999838339, 1.8264810507160347, 1.9427616058014427, 0.7014295339746117, 0.20606112998073237, 0.5143352145327803, -0.34597927642063775, 0.2594379052182792, -0.05203689954085833, -0.5794522822029166, 0.4906495726071997, -0.034324583542975434, -0.14861293759475658, -0.12344152798253367, 0.025496591883733336, 1.284665133192642, -0.3804169340191194, 0.24490229273798853, 0.17606447338069542, -0.3615511014437435, -0.32667907511589545, -0.02539630711633685, -0.6837298769178322, 0.5233213940389638, -0.10356768274588322, 0.3255850390639937, 0.23127621992973585, -0.44519166728496085]\n"
                    ]
                }
            ],
            "source": [
                "cost_values = []\n",
                "\n",
                "\n",
                "def objective(parameters):\n",
                "\n",
                "    cost = cudaq.observe(dc_qaoa, ham, qubit_num, num_layers, parameters, coef,\n",
                "                         words).expectation()\n",
                "    cost_values.append(cost)\n",
                "    return cost\n",
                "\n",
                "\n",
                "# Optimize!\n",
                "optimal_expectation, optimal_parameters = optimizer.optimize(\n",
                "    dimensions=parameter_count, function=objective)\n",
                "\n",
                "print('optimal_expectation =', optimal_expectation)\n",
                "print('optimal_parameters =', optimal_parameters)"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "Sampling the circuit with the optimal parameters allows for the `most_probable` command to reveal the bitsting corresponding to the ideal graph partitioning solution. This indicates what sort of interactions are present in the ideal docking configuration"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 13,
            "metadata": {},
            "outputs": [
                {
                    "name": "stdout",
                    "output_type": "stream",
                    "text": [
                        "{ 011100:1 101010:6 110001:20 111000:199973 }\n",
                        "\n",
                        "The MVWCP is given by the partition:  111000\n"
                    ]
                }
            ],
            "source": [
                "shots = 200000\n",
                "\n",
                "counts = cudaq.sample(dc_qaoa,\n",
                "                      qubit_num,\n",
                "                      num_layers,\n",
                "                      optimal_parameters,\n",
                "                      coef,\n",
                "                      words,\n",
                "                      shots_count=shots)\n",
                "print(counts)\n",
                "\n",
                "print('The MVWCP is given by the partition: ', counts.most_probable())"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "<img src=\"./images/partition.png\" alt=\"dockin\" width=\"300\" />"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "The convergence of the optimization can be plotted below."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": 14,
            "metadata": {},
            "outputs": [
                {
                    "data": {
                        "image/png": "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gENNr1699Kc//Ul///vftWTJEg0ZMkSStGPHDiUlJVleIAAAQGUEHGqeeeYZrV69WmPGjNHEiRPVrl07SdI//vEP9e3b1/ICz/Tll1/quuuuU7NmzeRyufTBBx8E9XgAACB8BHxddvfu3bV+/fqztj/55JOKiIiwpKjyHDt2TOedd55GjBihG264IajHAgAA4aXKN5vJzMz0XdrduXNnXXDBBZYVVZ7Bgwdr8ODBQT8OAAAIPwGHmv3792vYsGFasmSJEhISJElHjx7V5Zdfrjlz5igxMdHqGqussLBQhYWFvvd5eXk2VgMAAIIp4DU199xzjwoKCvT999/r8OHDOnz4sLKyspSXl6d77703GDVWWUZGhuLj432vlJQUu0sCAABBEnComT9/vl544QV16tTJt61z586aMWOG5s2bZ2lx1TVhwgTl5ub6XtnZ2XaXBAAAgiTg009er1d16tQ5a3udOnV896+pKTwejzwej91llGnr/gK1a1Lf7jIAAHCMgGdqrrjiCo0dO1Z79uzxbdu9e7f++Mc/asCAAZYW52THCkvsLgEAAEcJeKbm+eef129+8xu1bt3at0YlOztbXbt21Ztvvml5gWcqKCjQ1q1bfe937NihtWvXqmHDhmrZsmVQjw0AAGq2gENNSkqKVq9erc8//1ybNm2SJHXq1EkDBw60vLhfWrVqlS6//HLf+/Hjx0uS0tPTNXv27KAf30oBPtgbAAD8iirdp8blcunKK6/UlVdeaXU9Ferfv7+MMSE9JgAACA+VXlOzaNEide7cucx7veTm5qpLly766quvLC0OAACgsiodap555hndeeediouLO2tffHy87r77bk2fPt3S4pxsb+5Ju0sAAMBRKh1q1q1bp6uvvrrc/VdddZUyMzMtKao2mL10p90lAADgKJUONfv27Svz/jSnRUZG6sCBA5YUBQAAEKhKh5rmzZsrKyur3P3fffedmjZtaklRAAAAgap0qLnmmms0efJknTx59lqQEydOaMqUKbr22mstLc7JuKQbAABrVfqS7kmTJum9997TueeeqzFjxqhDhw6SpE2bNmnGjBkqLS3VxIkTg1YoAABARSodapKSkvTNN99o1KhRmjBhgu9+MS6XS4MGDdKMGTOUlJQUtEIBAAAqEtDN91q1aqVPP/1UR44c0datW2WMUfv27dWgQYNg1edYhwqK7C4BAABHqdIdhRs0aKDevXtbXUutsnlfvt0lAADgKAE/pRsAAKAmItQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINQAAABHINTYyBhjdwkAADgGocZGizbtt7sEAAAcg1Bjo+0HjtldAgAAjkGoAQAAjkCoAQAAjhB2oWbGjBlq3bq16tatqz59+ujbb7+1u6Qqc7nsrgAAAOcIq1Dzf//3fxo/frymTJmi1atX67zzztOgQYO0fz8LbgEAqO3CKtRMnz5dd955p+644w517txZL774omJiYvTaa6+V2b6wsFB5eXl+r5qEK7oBALBO2ISaoqIiZWZmauDAgb5tbrdbAwcO1LJly8r8TEZGhuLj432vlJSUUJULAABCLGxCzcGDB1VaWqqkpCS/7UlJScrJySnzMxMmTFBubq7vlZ2dHYpSK401NQAAWCfS7gKCyePxyOPx2F0GAAAIgbCZqWncuLEiIiK0b98+v+379u1TcnKyTVVVT/bh4/J6WVgDAIAVwibUREVFqWfPnlq4cKFvm9fr1cKFC5WammpjZVX3xrIf9cA/vrO7DAAAHCFsQo0kjR8/XrNmzdIbb7yhjRs3atSoUTp27JjuuOMOu0ursn+u/knHi0rsLgMAgLAXVmtqhg0bpgMHDuiRRx5RTk6Ozj//fM2fP/+sxcPhpvMjn+njey5W1+bxdpcCAEDYchlTe+6WkpeXp/j4eOXm5iouLs7Svls//Em1Pn9Nt2S9kNbTomoAAHCOyv79DqvTT05We6IlAADBQagBAACOQKipIZipAQCgegg1Frm4XeNqfd6IVAMAQHUQaizSLKGu3SUAAFCrEWosMj+r7OdPAQCA0CDUAAAARyDUWMRVzUdus1AYAIDqIdRYpLr3MCTTAABQPYQaAADgCIQai1T39BMAAKgeQo1FatEjtAAAqJEINRapbqQhEwEAUD2EGotU/+QTqQYAgOog1NQQn2/cr315J+0uAwCAsEWosYgV8yzj5qy1oBcAAGonQk0N8sP+fLtLAAAgbBFqapATRaX66ocDKirx2l0KAABhh1BT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X5vvvvtOl1xyierWrauUlBT99a9/Dcn3C5XS0lJNnjxZbdq0UXR0tM455xw9/vjjfs/lYSzL9uWXX+q6665Ts2bN5HK59MEHH/jtD+W4zZ07Vx07dlTdunXVrVs3ffrpp5Z/35AzqJY5c+aYqKgo89prr5nvv//e3HnnnSYhIcHs27fP7tJsM2jQIPP666+brKwss3btWnPNNdeYli1bmoKCAl+bkSNHmpSUFLNw4UKzatUqc9FFF5m+ffv69peUlJiuXbuagQMHmjVr1phPP/3UNG7c2EyYMMHXZvv27SYmJsaMHz/ebNiwwTz33HMmIiLCzJ8/P6TfNxS+/fZb07p1a9O9e3czduxY33bGsfIOHz5sWrVqZYYPH25WrFhhtm/fbj777DOzdetWX5tp06aZ+Ph488EHH5h169aZ3/zmN6ZNmzbmxIkTvjZXX321Oe+888zy5cvNV199Zdq1a2duueUW3/7c3FyTlJRk0tLSTFZWlnnnnXdMdHS0eemll0L6fYPpiSeeMI0aNTIff/yx2bFjh5k7d66pX7++efbZZ31tGMuyffrpp2bixInmvffeM5LM+++/77c/VOO2dOlSExERYf7617+aDRs2mEmTJpk6deqY9evXB30MgolQU00XXnihGT16tO99aWmpadasmcnIyLCxqppl//79RpJZsmSJMcaYo0ePmjp16pi5c+f62mzcuNFIMsuWLTPGnPrFd7vdJicnx9dm5syZJi4uzhQWFhpjjHnwwQdNly5d/I41bNgwM2jQoGB/pZDKz8837du3NwsWLDCXXXaZL9QwjoF56KGHzMUXX1zufq/Xa5KTk82TTz7p23b06FHj8XjMO++8Y4wxZsOGDUaSWblypa/NvHnzjMvlMrt37zbGGPPCCy+YBg0a+Mb39LE7dOhg9VeyzZAhQ8yIESP8tt1www0mLS3NGMNYVtYvQ00ox+13v/udGTJkiF89ffr0MXfffbel3zHUOP1UDUVFRcrMzNTAgQN929xutwYOHKhly5bZWFnNkpubK0lq2LChJCkzM1PFxcV+49axY0e1bNnSN27Lli1Tt27dlJSU5GszaNAg5eXl6fvvv/e1ObOP022cNvajR4/WkCFDzvqujGNgPvroI/Xq1Us33XSTmjRpoh49emjWrFm+/Tt27FBOTo7fWMTHx6tPnz5+45mQkKBevXr52gwcOFBut1srVqzwtbn00ksVFRXlazNo0CBt3rxZR44cCfbXDIm+fftq4cKF2rJliyRp3bp1+vrrrzV48GBJjGVVhXLcnPp7T6iphoMHD6q0tNTvD4YkJSUlKScnx6aqahav16tx48apX79+6tq1qyQpJydHUVFRSkhI8Gt75rjl5OSUOa6n91XUJi8vTydOnAjG1wm5OXPmaPXq1crIyDhrH+MYmO3bt2vmzJlq3769PvvsM40aNUr33nuv3njjDUk/j0dFv885OTlq0qSJ3/7IyEg1bNgwoDEPdw8//LBuvvlmdezYUXXq1FGPHj00btw4paWlSWIsqyqU41Zem3Af11r1lG6E3ujRo5WVlaWvv/7a7lLCTnZ2tsaOHasFCxaobt26dpcT9rxer3r16qU///nPkqQePXooKytLL774otLT022uLry8++67euutt/T222+rS5cuWrt2rcaNG6dmzZoxlrAVMzXV0LhxY0VERJx1tcm+ffuUnJxsU1U1x5gxY/Txxx9r8eLFatGihW97cnKyioqKdPToUb/2Z45bcnJymeN6el9FbeLi4hQdHW311wm5zMxM7d+/XxdccIEiIyMVGRmpJUuW6H//938VGRmppKQkxjEATZs2VefOnf22derUSbt27ZL083hU9PucnJys/fv3++0vKSnR4cOHAxrzcPfAAw/4Zmu6deum2267TX/84x99M4qMZdWEctzKaxPu40qoqYaoqCj17NlTCxcu9G3zer1auHChUlNTbazMXsYYjRkzRu+//74WLVqkNm3a+O3v2bOn6tSp4zdumzdv1q5du3zjlpqaqvXr1/v98i5YsEBxcXG+P0ypqal+fZxu45SxHzBggNavX6+1a9f6Xr169VJaWprvZ8ax8vr163fWrQW2bNmiVq1aSZLatGmj5ORkv7HIy8vTihUr/Mbz6NGjyszM9LVZtGiRvF6v+vTp42vz5Zdfqri42NdmwYIF6tChgxo0aBC07xdKx48fl9vt/+cjIiJCXq9XEmNZVaEcN8f+3tu9UjnczZkzx3g8HjN79myzYcMGc9ddd5mEhAS/q01qm1GjRpn4+HjzxRdfmL179/pex48f97UZOXKkadmypVm0aJFZtWqVSU1NNampqb79py9Fvuqqq8zatWvN/PnzTWJiYpmXIj/wwANm48aNZsaMGY68FPlMZ179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",
                        "text/plain": [
                            "<Figure size 640x480 with 1 Axes>"
                        ]
                    },
                    "metadata": {},
                    "output_type": "display_data"
                }
            ],
            "source": [
                "import matplotlib.pyplot as plt\n",
                "\n",
                "x_values = list(range(len(cost_values)))\n",
                "y_values = cost_values\n",
                "\n",
                "plt.plot(x_values, y_values)\n",
                "\n",
                "plt.xlabel(\"Epochs\")\n",
                "plt.ylabel(\"Cost Value\")\n",
                "plt.show()"
            ]
        }
    ],
    "metadata": {
        "kernelspec": {
            "display_name": "Python 3",
            "language": "python",
            "name": "python3"
        },
        "language_info": {
            "codemirror_mode": {
                "name": "ipython",
                "version": 3
            },
            "file_extension": ".py",
            "mimetype": "text/x-python",
            "name": "python",
            "nbconvert_exporter": "python",
            "pygments_lexer": "ipython3",
            "version": "3.12.3"
        }
    },
    "nbformat": 4,
    "nbformat_minor": 4
}
